using System;
using System.Collections.Generic;
using System.Text;
using AUSRIS.Reports.Utilities.Deployment.Entities;
using AUSRIS.Reports.Utilities.Deployment.FileSystem;
using AUSRIS.Reports.Utilities.Deployment.Logging;
using AUSRIS.Reports.Utilities.Deployment.Target;
using AUSRIS.Reports.Utilities.Deployment.Target.Tasks;
using AUSRIS.Reports.Utilities.Deployment.Configuration;

namespace AUSRIS.Reports.Utilities.Deployment
{
	/// <summary>
	/// This implementation of <see cref="IPublisher"/>
	/// creates a proposed plan of what it will publish
	/// and logs the plan before executing.
	/// It checks out a group of files (specified by module-name 
	/// in the <see cref="DeploymentSet"/>) and then 
	/// compares each file's revision with what was
	/// previously published to determine if it needs
	/// to publish.
	/// </summary>
	public class TaskDrivenPublisher : IPublisher
	{
		#region Private Fields

		private bool error;
		private IReportServer reportServer;
		private IRepository repository;
		private ILogger logger;
		private IConfigurationDatabase configurationDatabase;
		private DeploymentSet currentDeploymentSet;
		private IList<Report> publishedReportLog;
		private IList<Report> skippedReportLog;
        private IList<Report> reportsForParameterUpdate;
		private IList<IReportServerTask> cleanupTasks;
		private IList<IReportServerTask> folderPublishTasks;
		private IList<IReportServerTask> dataSourcePublishTasks;
		private IList<IReportServerTask> filePublishTasks;
		private IList<IReportServerTask> policyPublishTasks;
		#endregion

		#region Public Properties

		public ILogger Logger
		{
			get
			{
				return logger;
			}
			set
			{
				logger = value;
			}
		}

		public IRepository Repository
		{
			get
			{
				return repository;
			}
			set
			{
				repository = value;
			}
		}

		public IReportServer ReportServer
		{
			get
			{
				return reportServer;
			}
			set
			{
				reportServer = value;
			}
		}

		public IConfigurationDatabase ConfigurationDatabase
		{
			get
			{
				return configurationDatabase;
			}
			set
			{
				configurationDatabase = value;
			}
		}

		#endregion

		#region Public Methods

		public void LoadDeploymentSet(string name)
		{
			this.Reset();
			this.Logger.LogMessage("Loading Deployment Set '{0}'", name);
			this.currentDeploymentSet = this.ConfigurationDatabase.GetDeploymentSet(name);
			if ( this.currentDeploymentSet == null )
			{
				this.error = true;
				this.Logger.LogException(new Exception("Could not load Deployment Set: " + name));
			}
			
			this.InitializeReportServer();
			this.CheckReportServer();
			
			this.GetLocalFileCopies();

			this.CreateCleanupTasks();
			this.CreateFolderPublishTasks();
			this.CreateDataSourcePublishTasks();
			this.CreateFilePublishTasks();

			this.Logger.LogNewLine();
			this.Logger.LogHeader("Planned Tasks to execute:");
			this.LogPlannedTaskMessages();
			this.Logger.LogNewLine();
		}

		public void Publish()
		{
			if ( this.currentDeploymentSet == null )
			{
				throw new InvalidOperationException("No Deployment Set Loaded");
			}
			this.BeginTransaction();
			this.ExecuteCleanupTasks();
			this.ExecuteFolderPublishTasks();
			this.ExecuteDataSourcePublishTasks();
			this.ExecuteFilePublishTasks();
			this.ExecutePolicyPublishTasks();
			this.CommitTransaction();
			this.UpdateReportParameters();
			this.UpdateModelDatasources();
			this.LogCompletionMessages();
			this.Logger.Flush();
		}

		#endregion

		#region Private Helper Methods

		private void Reset()
		{
			this.skippedReportLog = new List<Report>();
			this.publishedReportLog = new List<Report>();
			this.cleanupTasks = new List<IReportServerTask>();
			this.folderPublishTasks = new List<IReportServerTask>();
			this.dataSourcePublishTasks = new List<IReportServerTask>();
			this.filePublishTasks = new List<IReportServerTask>();
			this.policyPublishTasks = new List<IReportServerTask>();
            this.reportsForParameterUpdate = new List<Report>();
			this.error = false;
		}

		private void GetLocalFileCopies()
		{
			if ( !error )
			{
				try
				{
					this.Logger.LogNewLine();
					this.Logger.LogMessage("Checking out local copies of {0}", this.currentDeploymentSet.ModuleName);
					this.Repository.CheckoutModule(this.currentDeploymentSet.ModuleName, this.currentDeploymentSet.VersionTag);
				}
				catch ( Exception ex )
				{
					this.error = true;
					this.Logger.LogException(ex);
				}
			}
		}

		private void LogCompletionMessages()
		{
			if ( !error )
			{
				this.Logger.LogHeader("Publish Executed Successfully");
				this.Logger.LogSuccess("Processed {0} report(s)", this.skippedReportLog.Count + this.publishedReportLog.Count);
				this.Logger.LogSuccess("Published {0} report(s)", this.publishedReportLog.Count);
				this.Logger.LogWarning("Skipped {0} report(s)", this.skippedReportLog.Count);
				if ( this.publishedReportLog.Count > 0 )
				{
					this.Logger.LogNewLine();
					this.Logger.LogMessage("The following reports were published:");
					foreach ( Report report in this.publishedReportLog )
					{
						this.Logger.LogSuccess(report.Path);
					}
				}
			}
		}

		private void InitializeReportServer()
		{
			if ( !this.error )
			{
				this.Logger.LogMessage("Initializing report server {0} to {1}", this.ReportServer.ToString(), this.currentDeploymentSet.TargetServiceUrl);
				try
				{
					this.ReportServer.Initialize(this.currentDeploymentSet.TargetServiceUrl);
				}
				catch ( Exception ex )
				{
					this.Logger.LogException(ex);
					this.error = true;
				}
			}
		}

		private void CheckReportServer()
		{
			if ( !this.error )
			{
				this.Logger.LogMessage("Checking report server {0}", this.ReportServer.ToString());
				try
				{
					if ( !this.ReportServer.CanConnect() )
					{
						this.Logger.LogWarning("Could not connect to report server");
						this.error = true;
					}
				}
				catch ( Exception ex )
				{
					this.Logger.LogException(ex);
					this.error = true;
				}
			}
		}

		private void BeginTransaction()
		{
			if ( !error )
			{
				try
				{
					this.Logger.LogNewLine();
					this.Logger.LogMessage("Beginning Transaction");
					this.ReportServer.BeginTransaction();
				}
				catch ( Exception ex )
				{
					this.Logger.LogException(ex);
				}
			}
		}

		private void CommitTransaction()
		{
			if ( !error )
			{
				try
				{
					this.Logger.LogNewLine();
					this.Logger.LogSuccess("Committing Transaction");
					this.ReportServer.CommitTransaction();
				}
				catch ( Exception ex )
				{
					this.Logger.LogException(ex);
					this.Cancel();
				}
			}
		}

		private void Cancel()
		{
			this.error = true;

			this.Logger.LogNewLine();
			this.Logger.LogWarning("Rolling Back Transaction");

			this.ReportServer.RollbackTransaction();
			
			this.Logger.LogNewLine();
			this.Logger.LogWarning("Publish Failed");
		}

		private bool FileIsPublished(Folder folder, CatalogFile file)
		{
			Report report = file as Report;
			if ( report != null )
			{
				string lastRevisionPublished = this.ReportServer.GetReportRevision(report);
				return lastRevisionPublished == report.Revision;
			}
			Model model = file as Model;
			if ( model != null )
			{
				string lastRevisionPublished = this.ReportServer.GetModelRevision(model);
				return lastRevisionPublished == model.Revision;
			}
			return this.ReportServer.ItemExists(file);
		}

		private void LogItem(DeploymentItem item, bool wasPublished)
		{
			if ( item.GetType().Equals(typeof(Report)) )
			{
				if ( wasPublished )
				{
					this.publishedReportLog.Add((Report) item);
				}
				else
				{
					this.skippedReportLog.Add((Report) item);
				}
			}
		}

		private void LogPlannedTaskMessages()
		{
			if ( this.cleanupTasks.Count > 0 )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Clean-up tasks:");
				this.LogPlannedTasks(this.cleanupTasks);
			}
			if ( this.folderPublishTasks.Count > 0 )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Folder publish tasks:");
				this.LogPlannedTasks(this.folderPublishTasks);
			}
			if ( this.dataSourcePublishTasks.Count > 0 )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("DataSource publish tasks:");
				this.LogPlannedTasks(this.dataSourcePublishTasks);
			}
			if ( this.filePublishTasks.Count > 0 )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("File publish tasks:");
				this.LogPlannedTasks(this.filePublishTasks);
			}
			if ( this.policyPublishTasks.Count > 0 )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Policy change publish tasks:");
				this.LogPlannedTasks(this.policyPublishTasks);
			}
		}

		#endregion

		#region Task Creation

		private void CreateCleanupTasks()
		{
			if ( !this.error )
			{
				try
				{
					this.Logger.LogNewLine();
					this.Logger.LogMessage("Creating clean-up tasks");
					IList<Folder> topFolders = this.currentDeploymentSet.Folders.GetTopFolders();
					foreach ( Folder topFolder in topFolders )
					{
						this.CreateFolderCleanupTasks(topFolder);
					}
				}
				catch ( Exception ex )
				{
					this.Logger.LogException(ex);
					this.Cancel();
				}
			}
		}

		private void CreateFolderCleanupTasks(Folder folder)
		{
			Folder deploymentFolder = null;
			if ( this.currentDeploymentSet.Folders.ContainsKey(folder.Path) )
			{
				deploymentFolder = this.currentDeploymentSet.Folders[folder.Path];
			}
			if ( deploymentFolder == null )
			{
				this.cleanupTasks.Add(new DeleteTask(folder));
			}
			else
			{
				foreach ( CatalogFile file in this.ReportServer.GetFiles(folder) )
				{
					if ( !deploymentFolder.ContainsFile(file) )
					{
						this.cleanupTasks.Add(new DeleteTask(file));
					}
				}
				foreach ( Folder subFolder in this.ReportServer.GetFolders(folder) )
				{
					this.CreateFolderCleanupTasks(subFolder);
				}
			}
		}

		private void CreateFolderPublishTasks()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Creating Folder Publish Tasks");
				foreach ( Folder folder in this.currentDeploymentSet.Folders.GetFlatList() )
				{
					try
					{
						if ( !this.ReportServer.ItemExists(folder) )
						{
							this.folderPublishTasks.Add(new FolderPublishTask(folder));
						}
						else
						{
							this.CreatePolicyPublishTask(folder);
						}
					}
					catch ( Exception ex )
					{
						this.Logger.LogException( ex);
						this.Cancel();
						break;
					}
				}
			}
		}

		private void CreateDataSourcePublishTasks()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Creating Data Source Publish Tasks");
				foreach ( Folder folder in this.currentDeploymentSet.Folders.GetFlatList() )
				{
					if ( folder.Datasources.Count > 0 )
					{
						foreach ( DataSource dataSource in folder.Datasources )
						{
							try
							{
								if ( dataSource.Overwrite || !this.reportServer.ItemExists(dataSource) )
								{
									this.dataSourcePublishTasks.Add(new DataSourcePublishTask(dataSource));
								}
							}
							catch ( Exception ex )
							{
								this.Logger.LogException(ex);
								this.Cancel();
								break;
							}
						}
					}
				}
			}
		}

		private void CreateFilePublishTasks()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Creating File Publish Tasks");
				foreach ( Folder folder in this.currentDeploymentSet.Folders.GetFlatList() )
				{
					if ( !error )
					{
						if ( folder.Files.Count > 0 )
						{
							this.Logger.LogHeader("Examining Files in '{0}'", folder.Path);
							foreach ( CatalogFile file in folder.Files )
							{
								this.Logger.LogNewLine();
								this.Logger.LogMessage("Getting revision for '{0}'", file.Name);
								try
								{
									if ( !this.Repository.FileExists(file) )
									{
										this.Logger.LogWarning("File '{0}' not found", file.Name);
										this.LogItem(file, false);
										continue;
									}
									this.Repository.SetRevision(file);
									if ( file.GetType().Equals(typeof(Model)) )
									{
										this.Repository.SetDataSourceViewPath((Model) file);
									}
								}
								catch ( Exception ex )
								{
									this.Logger.LogException(ex);
									this.Cancel();
									break;
								}
								try
								{
									if ( this.FileIsPublished(folder, file) )
									{
										this.Logger.LogWarning("File '{0}', Revision '{1}' is already published", file.Name, file.Revision);
										this.LogItem(file, false);
										this.CreatePolicyPublishTask(file);
									}
									else
									{
										this.Logger.LogSuccess("Creating File Publish Task for '{0}', Revision '{1}'", file.Name, file.Revision);
										this.filePublishTasks.Add(new FilePublishTask(folder, file));
									}
                                    if (file.GetType().Equals(typeof(Report)))
                                    {
                                        this.reportsForParameterUpdate.Add(file as Report);
                                    }
								}
								catch ( Exception ex )
								{
									this.Logger.LogException(ex);
									this.Cancel();
									break;
								}
							}
						}
					}
					
				}
			}
		}

		private void CreatePolicyPublishTask(DeploymentItem item)
		{
			bool needToPublish = false;
			IList<ItemPolicy> existingPolicies = this.ReportServer.GetItemPolicies(item);
			if ( existingPolicies.Count != item.Policies.Count )
			{
				needToPublish = true;
			}
			else
			{
				List<ItemPolicy> existingList = new List<ItemPolicy>(existingPolicies);
				List<ItemPolicy> newList = new List<ItemPolicy>(item.Policies);
				existingList.Sort();
				newList.Sort();
				for ( int x = 0; x < existingList.Count; x++ )
				{
					if ( !existingList[x].Equals(newList[x]) )
					{
						needToPublish = true;
						break;
					}
				}
			}
			if ( needToPublish )
			{
				this.policyPublishTasks.Add(new PolicyPublishTask(item));
			}
		}

		private void ExecuteTasks(IList<IReportServerTask> tasks)
		{
			foreach ( IReportServerTask task in tasks)
			{
				this.Logger.LogMessage("Executing Task: {0}", task);
				try
				{
					task.Execute(this.ReportServer);
				}
				catch ( Exception ex )
				{
					this.Logger.LogException(ex);
					this.Cancel();
					break;
				}
				this.Logger.LogSuccess("Completed Task: {0}", task);
			}
		}

		private void LogPlannedTasks(IList<IReportServerTask> tasks)
		{
			foreach ( IReportServerTask task in tasks )
			{
				this.Logger.LogMessage(task.ToString());
			}
		}

		#endregion

		#region Task Execution

		private void ExecuteCleanupTasks()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Cleaning up items from server");
				this.ExecuteTasks(this.cleanupTasks);
			}
		}

		private void ExecuteFolderPublishTasks()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Publishing Folders");
				this.ExecuteTasks(this.folderPublishTasks);
			}
		}

		private void ExecutePolicyPublishTasks()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Publishing Policy Changes");
				this.ExecuteTasks(this.policyPublishTasks);
			}
		}

		private void ExecuteDataSourcePublishTasks()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Publishing DataSources");
				this.ExecuteTasks(this.dataSourcePublishTasks);
			}
		}

		private void ExecuteFilePublishTasks()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Publishing Files");
				this.ExecuteTasks(this.filePublishTasks);

				if ( !error )
				{
					foreach ( FilePublishTask task in this.filePublishTasks )
					{
						this.LogItem(task.File, true);
					}
				}
			}
		}

		#endregion

		private void UpdateReportParameters()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Updating report parameters on server");
				foreach ( Report report in this.reportsForParameterUpdate )
				{
					try
					{
						this.ReportServer.UpdateReportParameters(report);
					}
					catch ( Exception ex )
					{
						this.Logger.LogException(ex);
					}
					this.Logger.LogSuccess("Updated parameters for '{0}'", report.Name);
				}
			}
		}

		private void UpdateModelDatasources()
		{
			if ( !error )
			{
				this.Logger.LogNewLine();
				this.Logger.LogMessage("Updating model datasources on server");
				foreach ( FilePublishTask task in this.filePublishTasks )
				{
					Model model = task.File as Model;
					if ( model != null )
					{
						try
						{
							this.ReportServer.UpdateModelDataSource(model);
						}
						catch ( Exception ex )
						{
							this.Logger.LogException(ex);
						}
						this.Logger.LogSuccess("Updated datasource for '{0}'", model.Name);
					}
				}
			}
		}
	}
}